package org.anachronos.jeep.agent.attach;

import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStreamReader;

import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.anachronos.jeep.core.attach.LaunchAttachServer;
import org.anachronos.jeep.core.attach.VirtualMachineRemoteControl;

public class LaunchAttachServerImpl implements LaunchAttachServer {
	private Process process;
	private BufferedReader reader;
	private VirtualMachineControlMBean virtualMachineControl;

	@Override
	public VirtualMachineRemoteControl getVirtualMachineControl() {
		return virtualMachineControl;
	}

	@Override
	public VirtualMachineRemoteControl launch() throws Exception {
		final String[] cmdArray = buildCmdArray();
		process = new ProcessBuilder(cmdArray).redirectErrorStream(true).start();
		reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
		String jmxServiceUrl = null;
		while (jmxServiceUrl == null) {
			jmxServiceUrl = reader.readLine();
		}
		System.setProperty(VirtualMachineRemoteControl.OWN_JVMS_MARKER, 
				VirtualMachineRemoteControl.OWN_JVMS_MARKER);
		System.out.println(jmxServiceUrl);
	    final JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(jmxServiceUrl)); 
	    final MBeanServerConnection serverConnection = connector.getMBeanServerConnection();
	    final ObjectName objectName = new ObjectName(VirtualMachineRemoteControl.NAME);
	    virtualMachineControl = 
	    	JMX.newMBeanProxy(serverConnection, objectName, VirtualMachineControlMBean.class);
		return virtualMachineControl;
	}
	
	@Override
	public void stop() throws Exception {
		process.destroy();
		process.waitFor();
	}

	/**
	 * Just used for testing. Calling this method will throw an exception
	 * when the server is still running.
	 */
	int exitCode() {
		return process.exitValue();
	}
	
	private String[] buildCmdArray() {
		final String jdkHome = getJdkHome();
		final String javaBin = new StringBuilder()
			.append(jdkHome).append(File.separator).append("bin").toString();
		final String[] javaFiles = new File(javaBin).list(new FilenameFilter() {			
			@Override
			public boolean accept(final File dir, final String name) {
				return "java".equals(name) || "javaw.exe".equals(name);
			}
		});
		if (javaFiles.length != 1) {
			throw new IllegalStateException("Can't find java executable in " + javaBin);
		}
		final String java = new StringBuilder()
			.append(javaBin).append(File.separator).append(javaFiles[0]).toString();
		final String javaLib = new StringBuilder()
			.append(jdkHome).append(File.separator).append("lib").toString();
		final String[] toolsJarFiles = new File(javaLib).list(new FilenameFilter() {
			@Override
			public boolean accept(final File dir, final String name) {
				return "tools.jar".equals(name);
			}
		});
		if (toolsJarFiles.length != 1) {
			throw new IllegalStateException("Can't find tools.jar in " + javaLib);
		}
		final String toolsJar = new StringBuilder()
			.append(javaLib).append(File.separator).append(toolsJarFiles[0]).toString();
		final String classPath = new StringBuilder()
			.append(toolsJar).append(File.pathSeparator)
			.append(AttachServer.getJarFile()).toString();
		final String[] cmdArray = new String[] {
			java, "-classpath", classPath, AttachServer.class.getName()	
		};
		return cmdArray;
	}
	
	private String getJdkHome() {
		final String javaHome = System.getProperty("java.home");
		if (javaHome.endsWith("jre")) {
			return new File(javaHome).getParent();
		} else {
			return javaHome;
		}
	}
}
